package org.nutz.json.impl;

import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.nutz.conf.NutConf;
import org.nutz.json.JsonException;
import org.nutz.json.JsonParser;
import org.nutz.lang.Lang;
import org.nutz.lang.Nums;
import org.nutz.lang.util.NutMap;
import org.nutz.mapl.MaplCompile;

/**
 * 将json理解为Map+List,以Token的方式读取,避免回溯等操作
 * 
 * @author wendal(wendal1985@gmail.com)
 * 
 */
public class JsonCompileImplV2 implements JsonParser, MaplCompile<Reader> {

    @Override
    public Object parse(Reader reader) {
        return new JsonTokenScan(reader).read();
    }
}

final class JsonTokenScan {

    // private static final Log log = Logs.get();

    Reader reader;

    JsonToken token = new JsonToken();
    JsonToken nextToken = null;
    JsonToken nextToken2 = new JsonToken();

    static final Object END = new Object();
    static final Object COMMA = new Object();

    public JsonTokenScan(Reader reader) {
        this.reader = reader;
    }

    protected void _nextToken() {
        switch (token.type) {
        case MapStart:
        case MapEnd:
        case ListStart:
        case ListEnd:
        case MapPair:
        case Comma:
            return;
        case '\'':
            token.type = SimpleString;
            token.value = readString('\'');
            return;
        case '\"':
            token.type = SimpleString;
            token.value = readString('"');
            return;
        case ' ':
        case '\t':
        case '\n':
        case '\r':
            char c = 0;
            while (true) {
                c = nextChar();
                switch (c) {
                    case ' ':
                    case '\t':
                    case '\n':
                    case '\r':
                        continue;
                    default:
                }
                break;
            }
            token.type = c;
            _nextToken();
            return;
        case '/':
            // 看来是注释哦
            skipComment();
            nextToken();
            return;
        default:
            StringBuilder sb = new StringBuilder();
            sb.append((char) token.type);
            // 看来只是尝试找到结束字符了
            OUT: while (true) {
                c = nextChar();
                switch (c) {
                    case MapStart:
                    case MapEnd:
                    case ListStart:
                    case ListEnd:
                    case MapPair:
                    case Comma:
                        nextToken = nextToken2;
                        nextToken.type = c;
                        // log.debug("Break OtherString token : " + (char) c);
                        // log.debug("OtherString token : " + (char) token.type);
                        break OUT;
                    case ' ':
                    case '\t':
                    case '\r':
                    case '\n':
                        break OUT;
                    case '/':
                        skipComment();
                        break OUT;
                    default:
                }
                sb.append(c);
            }
            token.type = OtherString;
            token.value = sb.toString();
            // log.debug("OtherString Token > " + token.value);
            return;
        }
    }

    protected void nextToken() {
        if (nextToken != null) {
            token.type = nextToken.type;
            token.value = nextToken.value;
            nextToken = null;
            return;
        }
        token.type = nextChar();
        _nextToken();
        // log.debug("token: " + token);
    }

    protected void skipComment() {
        char c = nextChar();
        switch (c) {
        case '/': // 单行注释
            while (nextChar() != '\n') {}
            // nextToken();
            return;
        case '*':
            char c2 = c;
            while (true) {
                while ((c = nextChar()) != '/') {
                    c2 = c;
                }
                if (c2 == '*') {
                    return;
                }
            }
        default:
            throw unexpectChar(c);
        }
    }

    protected String readString(char endEnd) {
        StringBuilder sb = new StringBuilder();
        char c = 0;
        while ((c = nextChar()) != endEnd) {
            switch (c) {
                case '\\':
                    char c2 = parseSp();
                    if (c == c2 && NutConf.JSON_APPEND_ILLEGAL_ESCAPE) {
                        sb.append('\\');
                    }
                    c = c2;
                    break;
                default:
            }
            sb.append(c);
        }
        return sb.toString();
    }

    protected Map<String, Object> readMap() {
        Map<String, Object> map = new NutMap();
        boolean hasComma = false;
        OUT: while (true) {
            nextToken();
            switch (token.type) {
            case MapEnd:
                break OUT;
            case SimpleString:
            case OtherString:
                String key = token.value;
                // log.debug("key=" + key + " " + token);
                nextToken();
                if (token.type != MapPair) {
                    throw unexpectChar((char) token.type);
                }
                Object obj = readObject(MapEnd);
                if (obj == COMMA) {
                    if (hasComma) {
                        throw unexpectChar((char) Comma);
                    }
                    hasComma = true;
                    continue;
                }
                if (obj == END) {
                    throw unexpectChar((char) token.type);
                }
                map.put(key, obj);
                hasComma = false;
                break;
            case Comma:
                continue;
            default:
                throw unexpectChar((char) token.type);
            }
        }
        return map;
    }

    protected List<Object> readList() {
        List<Object> list = new ArrayList<Object>();
        boolean hasComma = false;
        while (true) {
            Object obj = readObject(ListEnd);
            if (obj == END) {
                break;
            }
            if (obj == COMMA) {
                if (hasComma) {
                    throw unexpectChar((char) Comma);
                }
                hasComma = true;
                continue;
            }
            list.add(obj);
            hasComma = false;
        }
        return list;
    }

    protected Object readObject(int endTag) {
        nextToken();
        switch (token.type) {
            case MapStart:
                return readMap();
            case ListStart:
                return readList();
            case SimpleString:
                return token.value;
            case OtherString:
                String value = token.value;
                int len = value.length();
                if (len == 0) {
                    return "";
                }
                switch (value.charAt(0)) {
                    case 't':
                        if ("true".equals(value)) {
                            return true;
                        }
                        break;
                    case 'f':
                        if ("false".equals(value)) {
                            return false;
                        }
                        break;
                    case 'n':
                        if ("null".endsWith(value)) {
                            return null;
                        }
                        break;
                    case 'u':
                        if ("undefined".endsWith(value)) {
                            return null;
                        }
                        break;
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                    case '.':
                    case '-':
                        // 看来是数字哦
                        if (token.value.length() > 0) {
                            switch (token.value.charAt(token.value.length() - 1)) {
                                case 'l':
                                case 'L':
                                    return toLong(token.value.substring(0, token.value.length() - 1));
                                case 'f':
                                case 'F':
                                    return Float.parseFloat(token.value.substring(0, token.value.length() - 1));
                                default:
                                    if (token.value.contains("e") || token.value.contains("E")) {
                                        return new BigDecimal(token.value);
                                    }
                                    if (token.value.contains(".")) {
                                        return Double.parseDouble(token.value);
                                    }
                            }
                        }
                        Number n = toLong(token.value);
                        if (n instanceof Long && Integer.MAX_VALUE >= n.longValue() && n.longValue() >= Integer.MIN_VALUE) {
                            return n.intValue();
                        }
                        return n;
                    default:
                }
                throw new JsonException(row, col, value.charAt(0), "Unexpect String = " + value);
            default:
                if (token.type == endTag) {
                    return END;
                }
                if (token.type == Comma) {
                    return COMMA;
                }
                throw unexpectChar((char) token.type);
        }
    }
    
    protected Number toLong(String value) {
        Nums.Radix r = Nums.evalRadix(value);
        try {
            return Long.parseLong(r.val, r.radix);
        } catch (Throwable e) {
            try {
                return Long.parseLong(value);
            }
            catch (NumberFormatException e1) {
                return new BigInteger(value);
            }
        }
    }

    public Object read() {
        int c = 0;
        boolean add = false;
        OUT: while (true) {
            c = readChar();
            switch (c) {
            case -1:
                return null;
            case ' ':
            case '\t':
            case '\n':
            case '\r':
                continue;
            case '/':
                skipComment();
                break;
            default:
                add = true;
                break OUT;
            }
        }

        switch (c) {
        case 'v':
        case '(':
            while (true) {
                int z = nextChar();
                if (z == '{') {
                    return readMap();
                }
                if (z == '[') {
                    return readList();
                }
            }
        case MapStart:
            return readMap();
        case ListStart:
            return readList();
        case '\'':
        case '"':
            return readString((char) c);
        default:
            nextToken = nextToken2;
            nextToken.type = OtherString;
            if (add) {
                nextToken.value = (char) c + Lang.readAll(reader);
            } else {
                nextToken.value = Lang.readAll(reader);
            }
            return readObject(-1);
        }
    }

    char nextChar() {
        int c = readChar();
        if (c == -1) {
            throw new JsonException("Unexpect EOF");
        }
        return (char) c;
    }

    protected char parseSp() {
        char c = nextChar();
        switch (c) {
        case 'n':
            return '\n';
        case 'r':
            return '\r';
        case 't':
            return '\t';
        case '\\':
            return '\\';
        case '\'':
            return '\'';
        case '\"':
            return '"';
        case '/':
            return '/';
        case 'u':
            char[] hex = new char[4];
            for (int i = 0; i < 4; i++) {
                hex[i] = nextChar();
            }
            return (char) Integer.valueOf(new String(hex), 16).intValue();
        case 'b': // 这个支持一下又何妨?
            return ' ';// 空格
        case 'v':
            return ' ';// replace by blackspace
        case 'f':
            return '\f';
        default:
            // 容忍非法转义
            if (NutConf.JSON_ALLOW_ILLEGAL_ESCAPE) {
                return c;
            }
            throw unexpectChar(c);
        }
    }

    int row = 1;
    int col = 0;

    private int readChar() {
        try {
            int c = reader.read();
            switch (c) {
            case -1:
                break;
            case '\n':
                row++;
                col = 0;
                break;
            default:
                col++;
                break;
            }
            return c;
        }
        catch (IOException e) {
            throw new JsonException(e);
        }
    }

    static final int MapStart = '{';
    static final int MapEnd = '}';
    static final int ListStart = '[';
    static final int ListEnd = ']';
    static final int MapPair = ':';
    static final int SimpleString = 0;
    static final int OtherString = 1;
    static final int Comma = ',';

    protected JsonException unexpectChar(char c) {
        return new JsonException(row, col, c, "Unexpect Char");
    }
}

class JsonToken {
    int type;
    String value;

    @Override
    public String toString() {
        return "[" + (char) type + " " + value + "]" + hashCode();
    }
}
